﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;

namespace SuperSerialization
{
    class Tokenizer
    {
        private TextReader reader;
        private Token nextToken;
        private const char commentSymbol = '#';

        public int Line { get; private set; }

        public Tokenizer(TextReader reader)
        {
            this.reader = reader;
        }

        public Token Next()
        {
            if (nextToken == null) return ParseToken();
            Token value = nextToken;
            nextToken = null;
            return value;
        }

        public Token Peek()
        {
            nextToken = ParseToken();
            return nextToken;
        }

        public bool HasNext()
        {
            return Peek() != Token.EOF;
        }

        private void SkipWhiteSpace()
        {
            int peek = reader.Peek();
            while (peek >= 0)
            {
                if (Char.IsWhiteSpace((char)peek))
                {
                    int next = reader.Read();
                    if ((char)next == '\n')
                        Line++;
                }
                else
                    return;
                peek = reader.Peek();
            }
        }

        private void SkipComment()
        {
            int next = reader.Read(); // Skip the first comment character
            next = reader.Read(); // The character after the comment
            while (next >= 0)
            {
                if ((char)next == '\n' || (char)next == commentSymbol) // Go until we reach another comment symbol or a new line
                {
                    Line++;
                    return;
                }
                next = reader.Read();
            }
        }

        private Token ParseToken()
        {
            if (nextToken != null) return nextToken;

            SkipWhiteSpace();

            int peek = reader.Peek();
            if (peek >= 0)
            {
                char c = (char)peek;
                if (c == commentSymbol) { SkipComment(); return ParseToken(); }
                if (Char.IsDigit(c)) return ParseNumber();
                if (Char.IsLetter(c)) return ParseIdentifier();
                else if (c == '\"') return ParseString();
                else return ParseSymbol();
            }
            return Token.EOF;
        }

        private Token ParseString()
        {
            StringBuilder sb = new StringBuilder();
            reader.Read();
            int next = reader.Read();
            bool escape = false;
            while (next >= 0 && ((char)next != '\"' || escape))
            {
                if (escape) escape = false;
                if ((char)next == '\\')
                    escape = true;
                sb.Append((char)next);
                next = reader.Read();
            }
            return new Token(TokenType.STRING, sb.ToString());
        }

        private bool IsValidIdentifierSymbol(char c)
        {
            return Char.IsLetterOrDigit(c) || c == '_' || c == '+' || c == '.';
        }

        private Token ParseIdentifier()
        {
            StringBuilder sb = new StringBuilder();
            int next = reader.Peek();
            while (next >= 0 && IsValidIdentifierSymbol((char)next))
            {
                sb.Append((char)reader.Read());
                next = reader.Peek();
            }

            String identifier = sb.ToString();
            if (identifier == "namespace")
            {
                return Token.NAMESPACE;
            }
            else if (identifier.ToLower() == "true")
            {
                return Token.TRUE;
            }
            else if (identifier.ToLower() == "false")
            {
                return Token.FALSE;
            }
            else if (identifier.ToLower() == "null")
            {
                return Token.NULL;
            }

            return new Token(TokenType.IDENTIFIER, identifier);
        }

        private Token ParseNumber()
        {
            StringBuilder sb = new StringBuilder();
            int next = reader.Peek();
            bool period = false;
            bool f = false;
            while (next >= -1 && (Char.IsDigit((char)next) || (char)next == '.' && !period || (char)next == 'f' && !f))
            {
                if ((char)next == '.') period = true;
                if ((char)next == 'f')
                {
                    reader.Read();
                    f = true;
                }
                else
                {
                    sb.Append((char)reader.Read());
                }
                next = reader.Peek();
            }
            string value = sb.ToString();
            if (f) return new Token(TokenType.FLOAT, value);
            if (period) return new Token(TokenType.DOUBLE, value);
            return new Token(TokenType.INTEGER, value);
        }

        private Token ParseSymbol()
        {
            char c = (char)reader.Read();
            switch (c)
            {
                case '{':
                    return Token.OPEN_BRACE;
                case '}':
                    return Token.CLOSE_BRACE;
                case ':':
                    return Token.ASSIGNMENT;
                case '[':
                    return Token.OPEN_SQUARE;
                case ']':
                    return Token.CLOSE_SQUARE;
                case ',':
                    return Token.SEPARATOR;
                case ';':
                    return Token.SEMICOLON;
                case '-':
                    return Token.MINUS;
            }
            return Token.ErrorToken("Unknown token: '"+c+"'");
        }
    }
}
